#ifndef __HTTP_UTIL_HPP__
#define __HTTP_UTIL_HPP__

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <istream>
#include <ostream>
#include <sys/types.h>
#include <sys/stat.h>

class Util
{
public:
    /* 字符串分割——将字符串in以字符串sep为标志分割，得到的子串放到out中 */
    static size_t Split(const std::string& in, const std::string& sep, std::vector<std::string>* out)
    {
        auto& array = *out;
        size_t pos = 0, offset = 0;// [pos, offset)
        while(pos < in.size())
        {
            offset = in.find(sep, pos);
            if(offset == std::string::npos)
            {
                array.push_back(in.substr(pos));
                break;
            }
            if(offset == pos)
            {
                pos = offset + 1;
                continue;
            }
            array.push_back(in.substr(pos, offset - pos));
            pos = offset + sep.size();
        }
        return array.size();
    }

    /* 读取文件内容——读取文件的所有内容，放到Buffer中 */
    static bool ReadFile(const std::string& filename, std::string* out)
    {
        // 二进制方式打开文件
        std::ifstream ifs(filename.c_str(), std::ios::binary);
        if(ifs.is_open() == false)
        {
            ERR_LOG("ReadFile failed: open %s failed", filename.c_str());
            return false;
        }
        // 获取文件中数据大小
        ifs.seekg(0, ifs.end);
        int length = ifs.tellg();
        ifs.seekg(0, ifs.beg);
        // 读取数据
        out->resize(length);
        ifs.read(&(*out)[0], length);
        if(ifs.good() == false)
        {
            ERR_LOG("ReadFile failed: read %s failed", filename.c_str());
            ifs.close();
            return false;
        }
        ifs.close();

        return true;
    }
    /* 向文件中写入数据 */
    static bool WriteFile(const std::string& filename, const std::string& in)
    {
        // 二进制方式打开文件，覆盖式写入
        std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
        if(ofs.is_open() == false)
        {
            ERR_LOG("WriteFile failed: open %s failed", filename.c_str());
            return false;
        }
        // 写入
        ofs.write(in.c_str(), in.size());
        if(ofs.good() == false)
        {
            ERR_LOG("WriteFile failed: write %s failed", filename.c_str());
            ofs.close();
            return false;
        }
        ofs.close();
        return true;
    }
    /* URL编码——避免URL中的资源路径与查询字符串中的特殊字符和HTTP请求中的特殊字符产生歧义
        编码格式：将特殊字符的ASCII码值转换为两个16进制字符（前缀为%），如C++转换为C%2B%2B。
        在RFC3986文档中规定的URL绝对不编码字符：.-_~ 字母 数字
        RFC396文档规定：编码格式：%HH
        W3C文档规定：查询字符串中的空格需要被转换为加号+ */
    static std::string UrlEncode(const std::string& url, bool convert_space_to_plus = false)
    {
        std::string res;
        for(auto& c : url)
        {
            if(c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c))
            {
                res += c;
                continue;
            }
            if(c == ' ' && convert_space_to_plus)
            {
                res += '+';
                continue;
            }
            char buf[4] = { 0 };
            snprintf(buf, 4, "%%%02X", c);
            res += buf;
        }

        return res;
    }

    /* 十六进制数字字母转十进制数 */
    static char HexToInt(const char c)
    {
        if(isdigit(c)) return c - '0';
        if(isupper(c)) return c - 'A' + 10;
        if(islower(c)) return c - 'a' + 10;

        return -1;
    }

    /* URL解码 */
    static std::string UrlDecode(const std::string& url, bool convert_plus_to_space)
    {
        std::string res;
        for(int i = 0; i < url.size(); ++i)
        {
            if(url[i] == '+' && convert_plus_to_space)
            {
                res += ' ';
                continue;
            }
            if(url[i] == '%' && (i + 2 < url.size()))
            {
                char c1 = HexToInt(url[i + 1]);
                char c2 = HexToInt(url[i + 2]);
                res += ((c1 << 4) + c2);
                i += 2;
                continue;
            }
            res += url[i];
        }

        return res;
    }
    /* 根据状态码获取状态码描述信息 */
    static std::string StatusCodeDescrib(int status)
    {
        std::unordered_map<int, std::string> status_msg = {
            {100, "Continue"},
            {101, "Switching Protocol"},
            {102, "Processing"},
            {103, "Early Hints"},
            {200, "OK"},
            {201, "Created"},
            {202, "Accepted"},
            {203, "Non-Authoritative Information"},
            {204, "No Content"},
            {205, "Reset Content"},
            {206, "Partial Content"},
            {207, "Multi-Status"},
            {208, "Already Reported"},
            {226, "IM Used"},
            {300, "Multiple Choice"},
            {301, "Moved Permanently"},
            {302, "Found"},
            {303, "See Other"},
            {304, "Not Modified"},
            {305, "Use Proxy"},
            {306, "unused"},
            {307, "Temporary Redirect"},
            {308, "Permanent Redirect"},
            {400, "Bad Request"},
            {401, "Unauthorized"},
            {402, "Payment Required"},
            {403, "Forbidden"},
            {404, "Not Found"},
            {405, "Method Not Allowed"},
            {406, "Not Acceptable"},
            {407, "Proxy Authentication Required"},
            {408, "Request Timeout"},
            {409, "Conflict"},
            {410, "Gone"},
            {411, "Length Required"},
            {412, "Precondition Failed"},
            {413, "Payload Too Large"},
            {414, "URI Too Long"},
            {415, "Unsupported Media Type"},
            {416, "Range Not Satisfiable"},
            {417, "Expectation Failed"},
            {418, "I'm a teapot"},
            {421, "Misdirected Request"},
            {422, "Unprocessable Entity"},
            {423, "Locked"},
            {424, "Failed Dependency"},
            {425, "Too Early"},
            {426, "Upgrade Required"},
            {428, "Precondition Required"},
            {429, "Too Many Requests"},
            {431, "Request Header Fields Too Large"},
            {451, "Unavailable For Legal Reasons"},
            {501, "Not Implemented"},
            {502, "Bad Gateway"},
            {503, "Service Unavailable"},
            {504, "Gateway Timeout"},
            {505, "HTTP Version Not Supported"},
            {506, "Variant Also Negotiates"},
            {507, "Insufficient Storage"},
            {508, "Loop Detected"},
            {510, "Not Extended"},
            {511, "Network Authentication Required"}};
        auto it = status_msg.find(status);
        if(it == status_msg.end()) return "Unknown";
        return it->second;
    }
    /* 根据文件后缀名获取mime */
    static std::string SufMime(const std::string& filename)
    {
        std::unordered_map<std::string, std::string> suffix_mime = {
            {".aac", "audio/aac"},
            {".abw", "application/x-abiword"},
            {".arc", "application/x-freearc"},
            {".avi", "video/x-msvideo"},
            {".azw", "application/vnd.amazon.ebook"},
            {".bin", "application/octet-stream"},
            {".bmp", "image/bmp"},
            {".bz", "application/x-bzip"},
            {".bz2", "application/x-bzip2"},
            {".csh", "application/x-csh"},
            {".css", "text/css"},
            {".csv", "text/csv"},
            {".doc", "application/msword"},
            {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
            {".eot", "application/vnd.ms-fontobject"},
            {".epub", "application/epub+zip"},
            {".gif", "image/gif"},
            {".htm", "text/html"},
            {".html", "text/html"},
            {".ico", "image/vnd.microsoft.icon"},
            {".ics", "text/calendar"},
            {".jar", "application/java-archive"},
            {".jpeg", "image/jpeg"},
            {".jpg", "image/jpeg"},
            {".js", "text/javascript"},
            {".json", "application/json"},
            {".jsonld", "application/ld+json"},
            {".mid", "audio/midi"},
            {".midi", "audio/x-midi"},
            {".mjs", "text/javascript"},
            {".mp3", "audio/mpeg"},
            {".mpeg", "video/mpeg"},
            {".mpkg", "application/vnd.apple.installer+xml"},
            {".odp", "application/vnd.oasis.opendocument.presentation"},
            {".ods", "application/vnd.oasis.opendocument.spreadsheet"},
            {".odt", "application/vnd.oasis.opendocument.text"},
            {".oga", "audio/ogg"},
            {".ogv", "video/ogg"},
            {".ogx", "application/ogg"},
            {".otf", "font/otf"},
            {".png", "image/png"},
            {".pdf", "application/pdf"},
            {".ppt", "application/vnd.ms-powerpoint"},
            {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
            {".rar", "application/x-rar-compressed"},
            {".rtf", "application/rtf"},
            {".sh", "application/x-sh"},
            {".svg", "image/svg+xml"},
            {".swf", "application/x-shockwave-flash"},
            {".tar", "application/x-tar"},
            {".tif", "image/tiff"},
            {".tiff", "image/tiff"},
            {".ttf", "font/ttf"},
            {".txt", "text/plain"},
            {".vsd", "application/vnd.visio"},
            {".wav", "audio/wav"},
            {".weba", "audio/webm"},
            {".webm", "video/webm"},
            {".webp", "image/webp"},
            {".woff", "font/woff"},
            {".woff2", "font/woff2"},
            {".xhtml", "application/xhtml+xml"},
            {".xls", "application/vnd.ms-excel"},
            {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
            {".xml", "application/xml"},
            {".xul", "application/vnd.mozilla.xul+xml"},
            {".zip", "application/zip"},
            {".3gp", "video/3gpp"},
            {".3g2", "video/3gpp2"},
            {".7z", "application/x-7z-compressed"}
        };
        size_t pos = filename.find_last_of('.');
        if(pos == std::string::npos) return "application/octet-stream";// 没有扩展名，这是一个二进制内容
        
        auto it = suffix_mime.find(filename.substr(pos));
        if(it == suffix_mime.end()) return "application/octet-stream";
        return it->second;
    }
    /* 判断一个文件是否是目录 */
    static bool IsDir(const std::string& filename)
    {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if(ret < 0) return false;
        return S_ISDIR(st.st_mode);
    }
    /* 判断一个文件是否是普通文件 */
    static bool IsRegular(const std::string& filename)
    {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if(ret < 0) return false;
        return S_ISREG(st.st_mode);
    }
    /* 判断请求资源路径是否有效 
        如资源路径为：/index.html，其中，最前面的'/'表示相对根目录，请求的资源只能在这个目录下，
        不能越界访问其他目录，如：/../source，这是不允许的 
        "/"是0层
        "/index.html"是1层
        "/dir/file"是2层 */
    static bool ValidPath(const std::string& path)
    {
        std::vector<std::string> sub_paths;
        Split(path, "/", &sub_paths);
        int dir_level = 0;// 目录层级
        for(auto& sub_path : sub_paths)
        {
            if(sub_path == "..")
            {
                --dir_level;
                if(dir_level < 0) return false;
            }
            else ++dir_level;
        }
        return true;
    }
};

#endif